/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2020-2022. All rights reserved.
 * Description:
 * Author: g00421808
 * Create: 6/24/2022
 * Notes:
 */

#ifndef JSBIND_NAPI_VALUE_H
#define JSBIND_NAPI_VALUE_H

#include <js_native_api.h>
#include <node_api.h>
#include <tuple>
#include <uv.h>

#include "jsbind/binder/napi/napi_callback_binder.h"
#include "jsbind/config.h"
#include "jsbind/value/napi/js_function.h"
#include "jsbind/policy/policy.h"
#include "jsbind/value/value.h"
#include "jsbind/class/class_wrapper.h"
#include "jsbind/callback/napi/callback.h"
#include "jsbind/callback/napi/safety_callback.h"
#include "jsbind/arg_storage/arg_storage.h"
#include "jsbind/overloader/napi/napi_overloader.h"
#include "jsbind/value/array_buffer.h"
#if JSBIND_USING_TRACING
# include <tracing/trace.h>
#else
# define FUNCTION_DTRACE()
#endif // JSBIND_USING_TRACING
namespace Jsb {

class NapiValueBase : public Value {
public:
    NapiValueBase(napi_env& env, napi_value& value) : env_(env), value_(value) {}

    ~NapiValueBase() {
    }

    // TODO: napi_create_external方法的3、4个入参是必选，与Node.js规范不一致，已提单跟踪，当前临时规避
    static void FinalizeTest(napi_env env, void* nativeInstance, void* finalizeHint)
    {
        const char* hint = reinterpret_cast<const char*>(finalizeHint);
        JSB_DLOG(DEBUG) << "FinalizeTest: " << (hint == nullptr ? "unknonwn" : hint);
    }

    operator Value*() override { return this; }

    // 获取对象引用
    void* GetDataReference() override {
        JSB_DCHECK(false);
        return nullptr;
    }

    operator char* () {
        return GetCString();
    }

    operator const char* () {
        return GetCString();
    }

    template<typename T>
    operator T*() {
        return reinterpret_cast<T*>(GetDataReference());
    }

protected:
    napi_env& env_;

    napi_value& value_;
};

// class base
class NapiObjectValue : public NapiValueBase {
public:
    using NapiValueBase::NapiValueBase;

    static bool CheckType(napi_env env, napi_value value) {
        napi_status status;
        napi_valuetype type;
        status = napi_typeof(env, value, &type);
        JSB_DCHECK(status == napi_ok);
        return type == napi_object;
    }

    static constexpr const char* ExpectedType() {
        return "object";
    }
};

template<typename T>
class NapiValue : public NapiObjectValue {
public:
    using NapiObjectValue::NapiObjectValue;

    static napi_value ToNapiValue(napi_env& env, T&& arg) {
        JSB_DLOG(DEBUG) << "ToNapiValue for object";
        napi_status status;

        std::unique_ptr<T> headArg = std::make_unique<T>(std::move(arg));
        napi_value external;
        static const char* hint = "BIDL-external"; // TODO: napi_create_external方法的3、4个入参是必选，与Node.js规范不一致，已提单跟踪，当前临时规避
        status = napi_create_external(env, headArg.release(), FinalizeTest, const_cast<char*>(hint), &external);
        JSB_DCHECK(status == napi_ok);

        const napi_ref* consRef = Class<T>::GetInstance().GetClassRefs();
        napi_value cons;
        status = napi_get_reference_value(env, *consRef, &cons);
        JSB_DCHECK(status == napi_ok);

        size_t argc = 2;
        napi_value typeFlag;
        napi_create_uint32(env, static_cast<uint32_t>(TypeFlag::IS_SHARED_PTR), &typeFlag);
        napi_value args[2] = {external, typeFlag};
        napi_value instance;
        status = napi_new_instance(env, cons, argc, args, &instance);
        JSB_DCHECK(status == napi_ok);
        return instance;
    }

    // 获取对象引用
    void* GetDataReference() override {
        return ClassWrapper<typename ValueDefiner<T>::RawType>::UnWrapper(env_, value_);
    }
};

template<typename T>
class NapiValue<T*> : public NapiObjectValue {
public:
    using NapiObjectValue::NapiObjectValue;

    static napi_value ToNapiValue(napi_env& env, T* arg) {
        JSB_DLOG(DEBUG) << "ToNapiValue for object pointer: " << arg;
        napi_status status;

        napi_value external;
        static const char* hint = "BIDL-external ptr"; // TODO: napi_create_external方法的3、4个入参是必选，与Node.js规范不一致，已提单跟踪，当前临时规避
        status = napi_create_external(env, arg, FinalizeTest, const_cast<char*>(hint), &external);
        JSB_DCHECK(status == napi_ok);

        const napi_ref* consRef = Class<T>::GetInstance().GetClassRefs();
        napi_value cons;
        status = napi_get_reference_value(env, *consRef, &cons);
        JSB_DCHECK(status == napi_ok);

        size_t argc = 2;
        napi_value typeFlag;
        napi_create_uint32(env, static_cast<uint32_t>(TypeFlag::IS_POINTER), &typeFlag);
        napi_value args[2] = {external, typeFlag};
        napi_value instance;
        status = napi_new_instance(env, cons, argc, args, &instance);
        JSB_DCHECK(status == napi_ok);
        return instance;
    }

    // 获取对象引用
    void* GetDataReference() override {
        return ClassWrapper<typename ValueDefiner<T>::RawType>::UnWrapper(env_, value_);
    }
};

// std::shared_ptr
template<typename T>
class NapiValue<std::shared_ptr<T>> : public NapiObjectValue {
public:
    using NapiObjectValue::NapiObjectValue;

    static napi_value ToNapiValue(napi_env& env, std::shared_ptr<T> arg) {
        JSB_DLOG(DEBUG) << "ToNapiValue for std::shared_ptr object pointer: " << arg.get();
        if (arg == nullptr) {
            return nullptr;
        }
        napi_status status;

        // 重新申请堆内存，避免 std::shared_ptr 生命周期结束释放对象
        std::unique_ptr<T> headArg = std::make_unique<T>(std::move(*arg));
        napi_value external;
        static const char* hint = "BIDL-external ptr"; // TODO: napi_create_external方法的3、4个入参是必选，与Node.js规范不一致，已提单跟踪，当前临时规避
        status = napi_create_external(env, headArg.release(), FinalizeTest, const_cast<char*>(hint), &external);
        JSB_DCHECK(status == napi_ok);


        // TODO: consRef 有些类可能会忘记 Binding，此处需要判空并新增定位手段
        const napi_ref* consRef = Class<T>::GetInstance().GetClassRefs();
        JSB_DLOG(DEBUG) << "ToNapiValue for std::shared_ptr object pointer consRef: " << consRef;
        napi_value cons;
        status = napi_get_reference_value(env, *consRef, &cons);
        JSB_DCHECK(status == napi_ok);

        size_t argc = 2;
        napi_value typeFlag;
        napi_create_uint32(env, static_cast<uint32_t>(TypeFlag::IS_SHARED_PTR), &typeFlag);
        napi_value args[2] = {external, typeFlag};
        napi_value instance;
        status = napi_new_instance(env, cons, argc, args, &instance);
        JSB_DCHECK(status == napi_ok);
        return instance;
    }

    // 获取对象引用
    std::shared_ptr<T> GetShared() {
        return ClassWrapper<typename ValueDefiner<T>::RawType>::GetShared(env_, value_);
    }
};

// bool
template<>
class NapiValue<bool> : public NapiValueBase {
public:
    using NapiValueBase::NapiValueBase;

    /// bool类型转napi_value
    static napi_value ToNapiValue(napi_env& env, bool value) {
        napi_status status;
        napi_value result;
        status = napi_get_boolean(env, value, &result);
        JSB_DCHECK(status == napi_ok);
        return result;
    }

    bool GetBool() const override {
        napi_status status;
        bool result = false;
        status = napi_get_value_bool(env_, value_, &result);
        JSB_DCHECK(status == napi_ok) << "status: " << status;
        return result;
    }

    static bool CheckType(napi_env env, napi_value value) {
        napi_status status;
        napi_valuetype type;
        status = napi_typeof(env, value, &type);
        JSB_DCHECK(status == napi_ok);
        return type == napi_boolean;
    }

    static constexpr const char* ExpectedType() {
        return "boolean";
    }
};

// number base
class NapiNumberValue : public NapiValueBase {
public:
    using NapiValueBase::NapiValueBase;

    static bool CheckType(napi_env env, napi_value value) {
        napi_status status;
        napi_valuetype type;
        status = napi_typeof(env, value, &type);
        JSB_DCHECK(status == napi_ok);
        return type == napi_number;
    }

    static constexpr const char* ExpectedType() {
        return "number";
    }
};

// int32_t
template<>
class NapiValue<int32_t> : public NapiNumberValue {
public:
    using NapiNumberValue::NapiNumberValue;

    /// int32_t 类型转napi_value
    static napi_value ToNapiValue(napi_env& env, int32_t value) {
        napi_status status;
        napi_value result;
        status = napi_create_int32(env, value, &result);
        JSB_DCHECK(status == napi_ok);
        return result;
    }

    int32_t GetInt() const override {
        napi_status status;
        int32_t result;
        status = napi_get_value_int32(env_, value_, &result);
        JSB_DCHECK(status == napi_ok);
        return result;
    }
};

// int64_t
template<>
class NapiValue<int64_t> : public NapiNumberValue {
public:
    using NapiNumberValue::NapiNumberValue;

    /// int64_t 类型转napi_value
    static napi_value ToNapiValue(napi_env& env, int64_t value) {
        napi_status status;
        napi_value result;
        status = napi_create_int64(env, value, &result);
        JSB_DCHECK(status == napi_ok);
        return result;
    }

    int64_t GetInt64() const override {
        napi_status status;
        int64_t result;
        status = napi_get_value_int64(env_, value_, &result);
        JSB_DCHECK(status == napi_ok);
        return result;
    }
};

// double
template<>
class NapiValue<double> : public NapiNumberValue {
public:
    using NapiNumberValue::NapiNumberValue;

    /// double类型转napi_value
    static napi_value ToNapiValue(napi_env& env, double value) {
        napi_status status;
        napi_value result;
        status = napi_create_double(env, value, &result);
        JSB_DCHECK(status == napi_ok);
        return result;
    }

    double GetDouble() const override {
        napi_status status;
        double result;
        status = napi_get_value_double(env_, value_, &result);
        JSB_DCHECK(status == napi_ok);
        return result;
    }
};

// float
template<>
class NapiValue<float> : public NapiNumberValue {
public:
    using NapiNumberValue::NapiNumberValue;

    /// float 类型转napi_value
    static napi_value ToNapiValue(napi_env& env, float value) {
        napi_status status;
        napi_value result;

        status = napi_create_double(env, static_cast<double>(value), &result);
        JSB_DCHECK(status == napi_ok);
        return result;
    }

    float GetFloat() const override {
        napi_status status;
        double result;
        status = napi_get_value_double(env_, value_, &result);
        JSB_DCHECK(status == napi_ok);
        return static_cast<float>(result);
    }
};

class NapiStringValue : public NapiValueBase {
public:
    using NapiValueBase::NapiValueBase;

    ~NapiStringValue() {
        if (cStr_ != nullptr) {
            delete[] cStr_;
        }
    }

    /// char* 类型转napi_value
    static napi_value ToNapiValue(napi_env& env, const char* value) {
        FUNCTION_DTRACE();
        napi_status status;
        napi_value result;
        status = napi_create_string_utf8(env, value, std::strlen(value), &result);
        JSB_DCHECK(status == napi_ok);
        return result;
    }

    char* GetCString() override {
        FUNCTION_DTRACE();
        napi_status status;
        size_t length = 0;
        status = napi_get_value_string_utf8(env_, value_, nullptr, 0, &length);
        JSB_DCHECK(status == napi_ok);
        JSB_DCHECK(cStr_ == nullptr);
        JSB_DCHECK((length+1) < std::numeric_limits<size_t>::max());
        cStr_ = new char[length+1];
        status = napi_get_value_string_utf8(env_, value_, cStr_, length+1, &length);
        JSB_DCHECK(status == napi_ok);
        return cStr_;
    }

    static bool CheckType(napi_env env, napi_value value) {
        napi_status status;
        napi_valuetype type;
        status = napi_typeof(env, value, &type);
        JSB_DCHECK(status == napi_ok);
        return type == napi_string;
    }

    static constexpr const char* ExpectedType() {
        return "string";
    }

protected:
    char* cStr_ = nullptr;
};

// char*
template<>
class NapiValue<char*> : public NapiStringValue {
public:
    using NapiStringValue::NapiStringValue;
};

// const char*
template<>
class NapiValue<const char*> : public NapiStringValue {
public:
    using NapiStringValue::NapiStringValue;
};

// const char [N]
template<size_t N>
class NapiValue<const char [N]> : public NapiStringValue {
public:
    using NapiStringValue::NapiStringValue;
};

// char [N]
template<size_t N>
class NapiValue<char [N]> : public NapiStringValue {
public:
    using NapiStringValue::NapiStringValue;
};

// std::string
template<>
class NapiValue<std::string> : public NapiStringValue {
public:
    using NapiStringValue::NapiStringValue;

    /// string类型转napi_value
    static napi_value ToNapiValue(napi_env& env, std::string value) {
        return NapiStringValue::ToNapiValue(env, value.c_str());
    }

    std::string GetString() const override {
        FUNCTION_DTRACE();
        napi_status status;
        size_t length = 0;
        status = napi_get_value_string_utf8(env_, value_, nullptr, 0, &length);
        JSB_DCHECK(status == napi_ok);
        JSB_DCHECK((length+1) < std::numeric_limits<size_t>::max());
        char *buf = new char[length+1];
        status = napi_get_value_string_utf8(env_, value_, buf, length+1, &length);
        JSB_DCHECK(status == napi_ok);
        std::string result = buf;
        delete[] buf;
        return result;
    }
};

// std::vector
template<typename T>
class NapiValue<std::vector<T>> : public NapiValueBase {
public:
    using NapiValueBase::NapiValueBase;

    static napi_value ToNapiValue(napi_env& env, std::vector<T> value) {
        napi_status status;
        napi_value result;

        status = napi_create_array(env, &result);
        JSB_DCHECK(status == napi_ok);
        for (uint32_t i = 0; i < value.size(); i++) {
            napi_value element = NapiValue<T>::ToNapiValue(env, value[i]);

            status = napi_set_element(env, result, i, element);
            JSB_DCHECK(status == napi_ok);
        }
        return result;
    }

    std::vector<T> GetVector();

    static bool CheckType(napi_env env, napi_value value) {
        napi_status status;
        bool result;
        status = napi_is_array(env, value, &result);
        JSB_DCHECK(status == napi_ok);
        return result;
    }

    static constexpr const char* ExpectedType() {
        return "array";
    }
};

// std::array
template<typename T, size_t N>
class NapiValue<std::array<T, N>> : public NapiValueBase {
public:
    using NapiValueBase::NapiValueBase;

    static napi_value ToNapiValue(napi_env& env, std::array<T, N> value) {
        napi_status status;
        napi_value result;

        status = napi_create_array(env, &result);
        JSB_DCHECK(status == napi_ok);
        for (uint32_t i = 0; i < value.size(); i++) {
            napi_value element = NapiValue<T>::ToNapiValue(env, value[i]);

            status = napi_set_element(env, result, i, element);
            JSB_DCHECK(status == napi_ok);
        }
        return result;
    }

    std::array<T, N> GetArray();

    static bool CheckType(napi_env env, napi_value value) {
        napi_status status;
        bool result;
        status = napi_is_array(env, value, &result);
        JSB_DCHECK(status == napi_ok);
        return result;
    }

    static constexpr const char* ExpectedType() {
        return "array";
    }
};

// std::vector<uint8_t>
template<>
class NapiValue<std::vector<uint8_t>> : public NapiValueBase {
public:
    using NapiValueBase::NapiValueBase;

    static napi_value ToNapiValue(napi_env& env, std::vector<uint8_t> value) {
        napi_status status;
        napi_value outputBuffer;
        void* outputPtr = nullptr;
        status = napi_create_arraybuffer(env, value.size(), &outputPtr, &outputBuffer);
        JSB_DCHECK(status == napi_ok);

        napi_value outputArray = nullptr;
        status = napi_create_typedarray(env, napi_uint8_array, value.size(), outputBuffer, 0, &outputArray);
        JSB_DCHECK(status == napi_ok);

        uint8_t* outputBytes = (uint8_t*)(outputPtr);
        uint8_t* buf = value.data();
        for (size_t i = 0; i < value.size(); i++) {
            outputBytes[i] = buf[i];
        }

        return outputArray;
    }

    std::vector<uint8_t> GetVector() {
        napi_status status;
        napi_env env = env_;
        napi_typedarray_type type;
        std::vector<uint8_t> result;
        void * data;
        napi_value buffer;
        size_t length;
        size_t offset;
        status = napi_get_typedarray_info(env, value_, &type, &length, &data, &buffer, &offset);
        JSB_DCHECK(status == napi_ok);

        if (type == napi_uint8_array) {
            uint8_t* bytesBegin = (uint8_t*)(data);
            uint8_t* bytesEnd = bytesBegin + length;
            result.assign(bytesBegin, bytesEnd);
            return result;
        }
        status = napi_throw_error(env, nullptr, "PAF: Can only support Uint8Array.");
        JSB_DCHECK(status == napi_ok);
        return result;
    }

    static bool CheckType(napi_env env, napi_value value) {
        napi_status status;
        napi_typedarray_type type;
        status = napi_get_typedarray_info(env, value, &type, nullptr, nullptr, nullptr, nullptr);
        JSB_DCHECK(status == napi_ok);
        return type == napi_uint8_array;
    }

    static constexpr const char* ExpectedType() {
        return "Uint8Array";
    }
};

// function base
class NapiFunctionValue : public NapiValueBase {
public:
    using NapiValueBase::NapiValueBase;

    static bool CheckType(napi_env env, napi_value value) {
        napi_status status;
        napi_valuetype type;
        status = napi_typeof(env, value, &type);
        JSB_DCHECK(status == napi_ok);
        return type == napi_function;
    }

    static constexpr const char* ExpectedType() {
        return "function";
    }
};

// std::function
template<typename R, typename... P>
class NapiValue<std::function <R (P...)>> : public NapiFunctionValue {
public:
    using NapiFunctionValue::NapiFunctionValue;

    /// bool类型转napi_value
    static napi_value ToNapiValue(napi_env& env, std::function<R (P...)> value) {
        napi_status status;
        napi_value result;
        auto invoke = std::make_unique<std::function<R (P...)>>(std::move(value));
        status = napi_create_function(env, "", NAPI_AUTO_LENGTH, NapiCallbackBinder<R, P...>::Wrapper, invoke.release(), &result);
        JSB_DCHECK(status == napi_ok);
        return result;
    }

    std::function<R(P...)> GetFunction()
    {
        return SafetyCallback<R (P...)>(env_, value_);

    }
};

// napi_value
template<>
class NapiValue<napi_value> : public NapiValueBase {
public:
    using NapiValueBase::NapiValueBase;

    static napi_value ToNapiValue(napi_env& env, napi_value value) {
        return value;
    }
};

// Callback<R>
template<typename R, typename... P>
class NapiValue<Callback<R (P...)>> : public NapiFunctionValue {
public:
    using NapiFunctionValue::NapiFunctionValue;

    Callback<R (P...)> GetFunction() const
    {
        return Callback<R (P...)>(env_, value_);
    }
};

// SafetyCallback<R(P...)>
template<typename R, typename... P>
class NapiValue<SafetyCallback<R (P...)>> : public NapiFunctionValue {
public:
    using NapiFunctionValue::NapiFunctionValue;

    SafetyCallback<R(P...)> GetFunction() const
    {
        return SafetyCallback<R(P...)>(env_, value_);
    }
};

// Equivalent type from JavaScript
template<typename T>
class NapiValue<Equivalence<T>> : public NapiValueBase {
public:
    using NapiValueBase::NapiValueBase;

    void* GetDataReference() override {
        JSB_DLOG(DEBUG) << "NapiValue<Equivalence<T>>::GetDataReference";
        napi_env env = env_;

        napi_value str;
        napi_create_string_utf8(env, "equals", NAPI_AUTO_LENGTH, &str);
        napi_value equals;
        napi_get_property(env,
                          value_,
                          str,
                          &equals);
        napi_valuetype type;
        napi_typeof(env, equals, &type);
        JSB_DCHECK(type == napi_function);

        Callback<void (napi_value)> converter(env, equals);

        TemplatedArgStorage<T> storage(Class<T>::GetInstance().GetValueConstructorGroupId());

        napi_value target = value_;
        napi_value storageFunc;
        napi_create_function(env,
                             "",
                             NAPI_AUTO_LENGTH,
                             NapiOverloader::CreateValue,
                             &storage,
                             &storageFunc);

        converter.CallMethod(env, target, storageFunc);

        return storage.TaskClass();
    }
};

template<>
class NapiValue<JSFunction> : public NapiFunctionValue {
public:
    using NapiFunctionValue::NapiFunctionValue;

    JSFunction GetJSFunction() {
        return JSFunction(env_, value_);
    }
};

// ArrayBuffer
template<>
class NapiValue<ArrayBuffer> : public NapiValueBase {
public:
    using NapiValueBase::NapiValueBase;

    static napi_value ToNapiValue(napi_env env, ArrayBuffer value) {
        napi_status status;
        napi_value arrayBuffer;
        uint8_t* outputBuff = nullptr;

        status = napi_create_arraybuffer(env, value.length(), reinterpret_cast<void**>(&outputBuff), &arrayBuffer);
        JSB_DCHECK(status == napi_ok);

        uint8_t* inputBytes = value.data();
        for (size_t i = 0; i < value.length(); i++) {
            outputBuff[i] = inputBytes[i];
        }

        if (value.typed() != ArrayBuffer::BUFF) {
            napi_value typedArray;
            napi_typedarray_type typed = static_cast<napi_typedarray_type>(value.typed());

            status = napi_create_typedarray(env, typed, value.count(), arrayBuffer, 0, &typedArray);
            JSB_DCHECK(status == napi_ok);
            arrayBuffer = typedArray;
        }

        return arrayBuffer;
    }

    ArrayBuffer GetArrayBuffer() {
        napi_status status;
        napi_env env = env_;
        void * data;
        size_t length;
        ArrayBuffer::Typed typed = ArrayBuffer::BUFF;

        bool isArrayBuffer;
        status = napi_is_arraybuffer(env, value_, &isArrayBuffer);
        JSB_DCHECK(status == napi_ok);

        if (isArrayBuffer) {
            status = napi_get_arraybuffer_info(env, value_, &data, &length);
            JSB_DCHECK(status == napi_ok);
        } else {
            napi_typedarray_type type;
            napi_value buffer;
            size_t offset;
            status = napi_get_typedarray_info(env, value_, &type, &length, &data, &buffer, &offset);
            JSB_DCHECK(status == napi_ok);
            typed = static_cast<ArrayBuffer::Typed>(type);
        }

        return ArrayBuffer(reinterpret_cast<uint8_t *>(data), length, typed);
    }

    static bool CheckType(napi_env env, napi_value value) {
        napi_status status;
        bool isArrayBuffer;
        status = napi_is_arraybuffer(env, value, &isArrayBuffer);
        JSB_DCHECK(status == napi_ok);
        bool isTypedArray;
        status = napi_is_typedarray(env, value, &isTypedArray);
        JSB_DCHECK(status == napi_ok);
        return isArrayBuffer || isTypedArray;
    }

    static constexpr const char* ExpectedType() {
        return "ArrayBuffer | TypedArray";
    }
};

} // namespace Jsb
#endif //JSBIND_NAPI_VALUE_H
